home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Aminet 1 (Walnut Creek)
/
Aminet - June 1993 [Walnut Creek].iso
/
aminet
/
util
/
edit
/
jed207.lha
/
src
/
jed.lha
/
command.c
< prev
next >
Wrap
C/C++ Source or Header
|
1993-01-06
|
20KB
|
920 lines
/*
* COMMAND.C
* (c) 1992-3 J.Harper
*
* Wack(ish) style command processor
*
* explanation time,
* Each command string is built from clauses, the different types of
* clause are,
*
* (symbol [arg clauses]) command or variable clause
* `string' string clause (quotes nest!), strings can also
* {string} be surrounded by curly brackets (to help ARexx
* users :-)
* number number (hex, octal or decimal) clause,
* ~x ascii value of char x (number clause)
* ^x control codes
* @ clause with no value (a placeholder?)
*
* escape sequences supported in strings (and characters) are
* \n newline
* \r carriage return
* \f form feed
* \t horizontal tab
* \\ backslash
* \` ignored open quote
* \' ignored close quote
* \0377 octal byte
* \0xff hex byte
* \255 decimal byte
*
* There are two variable types, strings and numbers (32 bit signed ints),
* actually command functions are treated as variables, this is not important.
*
* In a command clause there can be more clauses representing that command's
* arguments, so an example command string could be,
* (settitle (format `%s %ld' `a string' 0x100))
*
* If a command string is to be passed as an argument it must be enclosed
* in quotes to stop it being executed too soon, ie,
* (if 1 `(req `foo\nbar\n' `go on')')
*
* internal notes:
* Command functions which return NULL are taken as signifying that the macro
* currently being evaluated is to be aborted, the Value in the RES field
* of that command is taken as the result of the whole macro (this is to
* make a (return) command easy :-)
*/
#include "jed.h"
#include "jed_protos.h"
Prototype GSYM * addgsym (STRPTR, BYTE, VALUE *);
Prototype LSYM * addlsym (STRPTR, BYTE, VALUE *);
Prototype BOOL remsym (STRPTR);
Local VALUE * resolveclause (STRPTR *, VALUE *);
Local BOOL buildargv (STRPTR *, LONG *, VALUE **);
Local VALUE * resolvesym (STRPTR, LONG, VALUE *, VALUE *);
Local VALUE * execsym (SYM *, LONG, VALUE *, VALUE *);
Local BOOL expandargs (STRPTR, LONG, VALUE *, LONG);
Prototype VOID releasevalue (VALUE *);
Prototype BOOL dupvalue (VALUE *, VALUE *);
Local VOID releaseargs (LONG, VALUE *);
Prototype BOOL templatecmd (LONG, VALUE *, LONG);
Prototype VALUE * execstr (STRPTR, VALUE *, BOOL, LONG, VALUE *);
Prototype LSYM * findlsym (STRPTR);
Prototype BOOL findsargs (LONG *, VALUE **);
Prototype VOID rxexecstring (STRPTR, APTR);
Prototype BOOL scriptfile (STRPTR);
Local STRPTR nextclause (STRPTR);
Prototype VALUE * cmd_symboldump (LONG, VALUE *);
Local VOID dumponesym (SYM *, BPTR);
Prototype const UBYTE NoArgMsg[];
Prototype const UBYTE NotStringMsg[];
Prototype const UBYTE NotNumerMsg[];
Prototype const UBYTE BadArgMsg[];
const UBYTE NoArgMsg[] = "syntax error: no argument specified";
const UBYTE NotStringMsg[] = "syntax error: not string value";
const UBYTE NotNumerMsg[] = "syntax error: not numerical value";
const UBYTE BadArgMsg[] = "syntax error: incorrect argument given";
Prototype HASHNODE *SymbolTab[GSYMTABSIZE];
Prototype CS CSList[];
Prototype WORD CSDepth;
HASHNODE *SymbolTab[GSYMTABSIZE];
CS CSList[MAX_CS_RECURSE];
WORD CSDepth;
/*
* value (any strings) will be allocated or whatever.
*/
GSYM *
addgsym(STRPTR name, BYTE symType, VALUE *value)
{
GSYM *newsym;
/*
* if(newsym = (GSYM *)findhash(SymbolTab, name))
* {
* releasevalue(&newsym->gs_Sym.sym_Value);
* newsym->gs_Sym.sym_Type = symType;
* dupvalue(&newsym->gs_Sym.sym_Value, value);
* return(newsym);
* }
* else
*/
if(newsym = AllocVec(sizeof(GSYM), MEMF_CLEAR))
{
if(newsym->gs_Node.h_Name = savestring(name))
{
inserthash(SymbolTab, &newsym->gs_Node, GSYMTABSIZE);
newsym->gs_Sym.sym_Type = symType;
dupvalue(&newsym->gs_Sym.sym_Value, value);
return(newsym);
}
FreeVec(newsym);
}
settitle(NoMemMsg);
return(FALSE);
}
/*
* value (any strings) will be allocated or whatever.
*/
LSYM *
addlsym(STRPTR name, BYTE symType, VALUE *value)
{
LSYM *newsym;
/*
* if(newsym = findlsym(name))
* {
* releasevalue(&newsym->ls_Sym.sym_Value);
* newsym->ls_Sym.sym_Type = symType;
* dupvalue(&newsym->ls_Sym.sym_Value, value);
* return(newsym);
* }
* else
*/
if(newsym = AllocVec(sizeof(LSYM), MEMF_CLEAR))
{
if(newsym->ls_Node.ln_Name = savestring(name))
{
newsym->ls_Sym.sym_Type = symType;
dupvalue(&newsym->ls_Sym.sym_Value, value);
AddTail(&CSList[CSDepth].cs_Locals, &newsym->ls_Node);
return(newsym);
}
FreeVec(newsym);
}
settitle(NoMemMsg);
return(FALSE);
}
/*
* symbol's value is automatically discarded
*/
BOOL
remsym(STRPTR name)
{
BOOL rc = FALSE;
GSYM *gsym;
LSYM *lsym;
if(lsym = findlsym(name))
{
Remove(&lsym->ls_Node);
freestring(lsym->ls_Node.ln_Name);
releasevalue(&lsym->ls_Sym.sym_Value);
FreeVec(lsym);
rc = TRUE;
}
else if(gsym = findgsym(name))
{
removehash(SymbolTab, &gsym->gs_Node, GSYMTABSIZE);
releasevalue(&gsym->gs_Sym.sym_Value);
FreeVec(gsym);
rc = TRUE;
}
else
settitlefmt("error: unknown symbol %s", (LONG)name);
return(rc);
}
/*
* Evaluate the next clause in the string (deals with all other clauses
* included in this clause).
*
* If this command returns FALSE it means a relatively major problem,
* normally syntax errors but it could be that the last window was closed
* in the middle of a command string, or that a clause was screwed up (ie,
* unknown symbol or something), or we ran out of stack.
*/
Local VALUE *
resolveclause(STRPTR *clause, VALUE *result)
{
STRPTR symbol = *clause;
UBYTE c;
if(stksize() <= MIN_STACK)
{
settitle("error: no stack available for deeper recursion");
goto error;
}
symbol = nextclause(symbol);
c = *symbol;
if((c == '(') || isalpha(c) || (c == '_'))
{
UBYTE name[40], d;
static const UBYTE dummy[] = ")";
STRPTR tname = name, dummy_p = dummy;
VALUE *argv;
LONG argc;
if(c == '(')
symbol = nextclause(symbol + 1);
while((d = *symbol) && (!isspace(d)) && (d != ')'))
{
*tname++ = d;
symbol++;
}
*tname = 0;
if(c == '(' ? buildargv(&symbol, &argc, &argv) : buildargv(&dummy_p, &argc, &argv))
{
result = resolvesym(name, argc, argv, result);
releaseargs(argc, argv);
}
}
else if((c == '`') || (c == '{'))
{
STRPTR tempbuff = AllocVec(1024, 0L);
LONG buffsize = 1024;
if(tempbuff)
{
WORD kg = 0;
WORD quotecount = 1;
LONG i = 0;
symbol++;
while(!kg)
{
UBYTE c = *symbol++;
switch(c)
{
case 0:
kg = -1;
symbol--;
settitle("syntax error: unbalanced quotes in string clause");
break;
case '`':
case '{':
quotecount++;
break;
case '\'':
case '}':
if(!(--quotecount))
{
kg = 1;
c = 0;
}
break;
case '\\':
if(quotecount == 1)
c = escchar(&symbol);
else
{
if(i >= buffsize)
{
STRPTR newbuff = AllocVec(buffsize << 1, 0L);
if(!newbuff)
{
settitle(NoMemMsg);
FreeVec(tempbuff);
goto error;
}
memcpy(newbuff, tempbuff, buffsize);
FreeVec(tempbuff);
tempbuff = newbuff;
buffsize <<= 1;
}
tempbuff[i++] = c;
c = *symbol++;
}
break;
}
if(i >= buffsize)
{
STRPTR newbuff = AllocVec(buffsize << 1, 0L);
if(!newbuff)
{
settitle(NoMemMsg);
FreeVec(tempbuff);
goto error;
}
memcpy(newbuff, tempbuff, buffsize);
FreeVec(tempbuff);
tempbuff = newbuff;
buffsize <<= 1;
}
tempbuff[i++] = c;
}
if(kg == 1)
{
if(!(result->val_Value.String = savestring(tempbuff)))
{
settitle(NoMemMsg);
FreeVec(tempbuff);
goto error;
}
result->val_Type = VTF_STRING;
}
else
{
FreeVec(tempbuff);
goto error;
}
FreeVec(tempbuff);
}
else
{
settitle(NoMemMsg);
goto error;
}
}
else if((isdigit(c)) || (c == '-'))
{
result->val_Value.Number = strtol(symbol, &symbol, 0);
result->val_Type = VTF_NUMBER;
}
else if(c == '~')
{
if(symbol[1] == '\\')
{
symbol += 2;
result->val_Value.Number = (LONG)escchar(&symbol);
}
else
{
result->val_Value.Number = (LONG)symbol[1];
symbol += 2;
}
result->val_Type = VTF_NUMBER;
}
else if(c == '^')
{
result->val_Value.Number = (LONG)(toupper(symbol[1]) - '@');
result->val_Type = VTF_NUMBER;
symbol += 2;
}
else if(c == '@')
{
result->val_Type = VTF_NONE;
symbol++;
}
else
{
settitle("syntax error: unrecognized clause type");
error:
/* In a perfect world I'd just step over this clause, but since
* I'm lazy that can wait and for now I'll skip the whole string.
*/
symbol += strlen(symbol);
result->val_Type = VTF_NONE;
result = FALSE;
}
*clause = nextclause(symbol);
return(result);
}
/*
* build up the argv array
*
* Format of the argv array is,
* argv[0] - VALUE structure for result returned from command function,
* argv[1-n] - VALUE structure of each argument.
*
* The argc passed is the number of arguments, doesn't include the
* result.
* The number of arguments is only limited by memory availability.
*/
Local BOOL
buildargv(STRPTR *args_p, LONG *argc_p, VALUE **argv_p)
{
WORD maxargs = 9;
VALUE *argv = AllocVec(sizeof(VALUE) * maxargs, MEMF_CLEAR);
if(argv)
{
LONG i;
STRPTR args = *args_p;
for(i = 0; *args != ')'; i++)
{
if(i == (maxargs - 1))
{
VALUE *newargv = AllocVec(sizeof(VALUE) * (maxargs << 1), MEMF_CLEAR);
if(!newargv)
{
settitle(NoMemMsg);
releaseargs(i, argv);
return(FALSE);
}
memcpy(newargv, argv, sizeof(VALUE) * (i + 1));
FreeVec(argv);
argv = newargv;
maxargs <<= 1;
}
if(!(resolveclause(&args, &argv[i + 1])))
{
releaseargs(i, argv);
return(FALSE);
}
}
*args_p = args + 1;
*argv_p = argv;
*argc_p = i;
return(TRUE);
}
settitle(NoMemMsg);
return(FALSE);
}
/*
* find where a symbol is and resolve it
*/
Local VALUE *
resolvesym(STRPTR name, LONG argc, VALUE *argv, VALUE *result)
{
LSYM *lsym;
GSYM *gsym;
UBYTE tmpbuf[256];
/* Try local symbols first
*/
if(lsym = findlsym(name))
result = execsym(&lsym->ls_Sym, argc, argv, result);
/* Look for a global symbol
*/
else if(gsym = findgsym(name))
result = execsym(&gsym->gs_Sym, argc, argv, result);
/* See if we can find a DOS variable which fits
*/
else if(GetVar(name, tmpbuf, 256, 0) > 0)
{
if(result->val_Value.String = savestring(tmpbuf))
result->val_Type = VTF_STRING;
else
{
settitle(NoMemMsg);
result->val_Type = VTF_NONE;
result = FALSE;
}
}
/* Try for implicit REXX macro
*/
else
{
if(strlen(name) < 200)
{
BPTR lock;
sprintf(tmpbuf, "REXX:%s.jed", name);
if(lock = Lock(tmpbuf, SHARED_LOCK))
{
UnLock(lock);
strcpy(tmpbuf, name);
if(expandargs(tmpbuf, argc, argv, 256))
{
if(!(result->val_Value.Number = asyncRexxCmd(tmpbuf)))
goto nosym;
result->val_Type = VTF_NUMBER;
}
else
{
settitle("error: buffer overflow");
result = FALSE;
}
}
else
goto nosym;
}
else
{
settitle(NoMemMsg);
result = FALSE;
}
}
if(FALSE)
{
nosym:
settitlefmt("error: can't find symbol %s", (LONG)name);
result = FALSE;
}
return(result);
}
/*
* actually executes (or whatever) a symbol
*/
Local VALUE *
execsym(SYM *sym, LONG argc, VALUE *argv, VALUE *result)
{
switch(sym->sym_Value.val_Type)
{
case VTF_FUNC:
if(!sym->sym_Value.val_Value.Func(argc, argv))
{
*result = RES;
result = NULL;
}
else
*result = RES;
refresh();
break;
case VTF_STRING:
if(sym->sym_Type == STF_COMMAND)
result = execstr(sym->sym_Value.val_Value.String, result, TRUE, argc, argv);
else
dupvalue(result, &sym->sym_Value);
break;
default:
dupvalue(result, &sym->sym_Value);
break;
}
return(result);
}
/*
* expands a commands argument clauses into a string (for REXX)
*/
Local BOOL
expandargs(STRPTR dest, LONG argc, VALUE *argv, LONG maxd)
{
LONG i = strlen(dest);
while(argc--)
{
switch(argv->val_Type)
{
case VTF_STRING:
LONG strl = strlen(argv->val_Value.String);
if(i + strl < maxd)
{
strcpy(dest + i, " ");
strcpy(dest + i + 1, argv->val_Value.String);
i += strl + 1;
}
else
{
settitle("error: internal buffer overflow");
return(FALSE);
}
break;
case VTF_NUMBER:
UBYTE nbuf[13];
LONG strl;
sprintf(nbuf, "%ld", argv->val_Value.Number);
strl = strlen(nbuf);
if(i + strl < maxd)
{
strcpy(dest + i, " ");
strcpy(dest + i + 1, nbuf);
i += strl + 1;
}
else
return(FALSE);
break;
}
argv++;
}
return(TRUE);
}
VOID
releasevalue(VALUE *value)
{
if(value->val_Type == VTF_STRING)
freestring(value->val_Value.String);
value->val_Type = VTF_NONE;
}
BOOL
dupvalue(VALUE *dst, VALUE *src)
{
BOOL rc = TRUE;
if(src->val_Type == VTF_STRING)
{
if(dst->val_Value.String = savestring(src->val_Value.String))
dst->val_Type = VTF_STRING;
else
{
dst->val_Type = VTF_NONE;
rc = FALSE;
}
}
else
*dst = *src;
return(rc);
}
/*
* Doesn't touch the result field
*/
Local VOID
releaseargs(LONG argc, VALUE *argv)
{
if(argv)
{
LONG i;
for(i = 1; i <= argc; i++)
{
if(argv[i].val_Type == VTF_STRING)
freestring(argv[i].val_Value.String);
}
FreeVec(argv);
}
}
/*
* checks the arguments passed to a command against the provided
* template. template has two bits per argument (ARG1 is LSB)
* these bits represent,
* 00 anything
* 01 string
* 10 number
* macros are provided for this function, ie
* if(TPLATE2(VTF_NUMBER, VTF_STRING))
*/
BOOL
templatecmd(LONG argc, VALUE *argv, LONG argTemplate)
{
LONG i;
for (i = 1; i <= argc; i++)
{
LONG thismask = argTemplate & 3;
switch(thismask)
{
case 1:
if(ARG(i).val_Type != VTF_STRING)
{
settitlefmt("syntax error: argument %ld should be a string", i);
return(FALSE);
}
break;
case 2:
if(ARG(i).val_Type != VTF_NUMBER)
{
settitlefmt("syntax error: argument %ld should be a number", i);
return(FALSE);
}
break;
}
argTemplate >>= 2;
}
if(argTemplate)
{
settitle("syntax error: too few arguments");
return(FALSE);
}
return(TRUE);
}
/*
* Executes a string of clauses, arguments can be passed and it can return
* a result.
*
* A command in the macro which returns a null VALUE only aborts a macro
* string (defined as those having arguments) - the result of the macro is
* the result of the command that returned NULL.
*
* internal:
* If this command returns 0 then it means that the result (in the VALUE
* passed in) should be propagated back through the strings being executed
* up to the macro (if available).
* If a command returns 0 and result type is VTF_BREAK we just break from
* this string returning a VTF_NONE value. Actually VTF_BREAK can handle
* multiple depth breaking.
*/
VALUE *
execstr(STRPTR string, VALUE *result, BOOL args, LONG argc, VALUE *argv)
{
if(++CSDepth < MAX_CS_RECURSE)
{
LSYM *thissym, *nextsym;
CS *cs = &CSList[CSDepth];
cs->cs_Args = args;
cs->cs_Argc = argc;
cs->cs_Argv = argv;
NewList(&cs->cs_Locals);
result->val_Type = VTF_NONE;
while(*string)
{
releasevalue(result);
if(!resolveclause(&string, result))
{
if(result->val_Type == VTF_BREAK)
{
if(!(--result->val_Value.Number))
result->val_Type = VTF_NONE;
else
result = FALSE;
}
else if(!args)
result = FALSE;
break;
}
}
for(thissym = (LSYM *)cs->cs_Locals.lh_Head; thissym->ls_Node.ln_Succ; thissym = nextsym)
{
nextsym = (LSYM *)thissym->ls_Node.ln_Succ;
freestring(thissym->ls_Node.ln_Name);
releasevalue(&thissym->ls_Sym.sym_Value);
FreeVec(thissym);
}
}
else
{
settitle("error: command string recursion too deep");
result->val_Type = VTF_NONE;
}
CSDepth--;
return(result);
}
LSYM *
findlsym(STRPTR name)
{
WORD i;
for(i = CSDepth; i; i--)
{
LSYM *this;
for(this = (LSYM *)CSList[i].cs_Locals.lh_Head; this->ls_Node.ln_Succ; this = (LSYM *)this->ls_Node.ln_Succ)
{
if(!strcmp(name, this->ls_Node.ln_Name))
return(this);
}
}
return(FALSE);
}
/*
* find the arguments given to the innermost macro
*/
BOOL
findsargs(LONG *argc_p, VALUE **argv_p)
{
WORD i;
for(i = CSDepth; i; i--)
{
if(CSList[i].cs_Args)
{
*argc_p = CSList[i].cs_Argc;
*argv_p = CSList[i].cs_Argv;
return(TRUE);
}
}
return(FALSE);
}
VOID
rxexecstring(STRPTR string, APTR rexxMsg)
{
VALUE result;
cursor(OFF);
if(execstr(string, &result, FALSE, 0, NULL))
{
if(result.val_Type == VTF_STRING)
replyRexxCmd(rexxMsg, 0, 0, result.val_Value.String);
else if(result.val_Type == VTF_NUMBER)
replyRexxCmd(rexxMsg, result.val_Value.Number, 0, NULL);
else
replyRexxCmd(rexxMsg, 10, 0, NULL);
}
else
replyRexxCmd(rexxMsg, 10, 0, NULL);
releasevalue(&result);
cursor(ON);
}
/*
* Used by _main() to execute startup script
*/
BOOL
scriptfile(STRPTR fileName)
{
STRPTR filetext;
if(filetext = squirrelfile(fileName))
{
VALUE result;
settitlefmt("executing %s...", (LONG)fileName);
cursor(OFF);
execstr(filetext, &result, FALSE, 0, NULL);
releasevalue(&result);
cursor(ON);
freestring(filetext);
return(TRUE);
}
return(FALSE);
}
/*
* Steps over white space, if a character COMMENT_CHAR (';') is found
* the rest of the line is skipped.
*
* IMPORTANT:
* If a command string is given to a command by enclosing it in quotes
* it may contain comments (which won't be stripped until the command is
* executed) but they (the comments) must NOT contain un-escaped quotes.
*/
Local STRPTR
nextclause(STRPTR str)
{
STRPTR str2 = str;
for(;;)
{
UBYTE c;
while(isspace(c = *str2++))
;
if(c == COMMENT_CHAR)
{
while((c = *str2++) && (c != '\n'))
;
if(!c)
str2--;
}
else
return(str2 - 1);
}
}
/*
* (symboldump `file' `type')
*
* type =
* globals
* locals
* all
*/
VALUE *
cmd_symboldump(LONG argc, VALUE *argv)
{
if(TPLATE2(VTF_STRING, VTF_STRING))
{
BPTR fh = Open(ARG1.val_Value.String, MODE_NEWFILE);
if(fh)
{
BOOL globs = FALSE;
BOOL locs = FALSE;
FPuts(fh, "symbol dump\n");
if(!stricmp("globals", ARG2.val_Value.String))
globs = TRUE;
else if(!stricmp("locals", ARG2.val_Value.String))
locs = TRUE;
else if(!stricmp("all", ARG2.val_Value.String))
{
globs = TRUE;
locs = TRUE;
}
if(globs)
{
WORD i;
FPuts(fh, "\nglobal symboltable\n");
for(i = 0; i < GSYMTABSIZE; i++)
{
GSYM *thissym = SymbolTab[i];
while(thissym)
{
FPrintf(fh, "\t%s", (LONG)thissym->gs_Node.h_Name);
dumponesym(&thissym->gs_Sym, fh);
thissym = thissym->gs_Node.h_Next;
}
}
}
if(locs)
{
WORD i;
FPuts(fh, "\nlocal symbols\n");
for(i = 1; i <= CSDepth; i++)
{
LSYM *thissym = (LSYM *)CSList[i].cs_Locals.lh_Head;
FPrintf(fh, "\tdepth %ld\n", i);
while(thissym->ls_Node.ln_Succ)
{
FPrintf(fh, "\t\t%s", (LONG)thissym->ls_Node.ln_Name);
dumponesym(&thissym->ls_Sym, fh);
thissym = thissym->ls_Node.ln_Succ;
}
}
}
Close(fh);
setnumres(TRUE);
}
else
setnumres(FALSE);
}
return(&RES);
}
Local VOID
dumponesym(SYM *sym, BPTR fh)
{
if(sym->sym_Type == STF_COMMAND)
FPuts(fh, " (command)");
else if(sym->sym_Type == STF_VARIABLE)
FPuts(fh, " (variable)");
else
FPuts(fh, " (unknown symbol type)");
if(sym->sym_Value.val_Type == VTF_NONE)
FPuts(fh, " = no value\n");
else if(sym->sym_Value.val_Type == VTF_STRING)
FPrintf(fh, " = {%s}\n", sym->sym_Value.val_Value.Number);
else if(sym->sym_Value.val_Type == VTF_NUMBER)
FPrintf(fh, " = 0x%lx\n", sym->sym_Value.val_Value.Number);
else if(sym->sym_Value.val_Type == VTF_FUNC)
FPuts(fh, " = primitive function\n");
else
FPuts(fh, " unknown value type\n");
}